{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第 2 章 找出两篇文章的不同\n",
    "*****"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "开发一个大模型工具，实现文档内容比对。阿里云的文档工程师们遇到过这样的问题：同一篇文档在中国站和国际站有一些不同，能快速的指出一批文档的这种差异吗？收到这个问题的你起初感到有些犯难，但想到或许可以借助大模型来快速完成这个任务，你又觉得似乎可以很快做到。\n",
    "\n",
    "本章我们将开发一个程序，该程序可以完成：\n",
    "- 可以指定文档 URL，程序根据 URL 自动爬取中国站、国际站的文档内容作比对\n",
    "- 因为要比对的文档很多，你希望是可以从一个 excel 文件中读取 URL 列表，并对其进行比对\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 准备工作\n",
    "\n",
    "### 1.1. 安装\n",
    "\n",
    "下载文档代码及安装依赖项\n",
    "```bash\n",
    "git clone https://github.com/AlibabaCloudDocs/llm_learning.git\n",
    "cd llm_learning\n",
    "pip install -r requirements.txt\n",
    "```\n",
    "\n",
    "### 1.2. 账号准备\n",
    "\n",
    "首先，您需要前往 [官网创建 API Key](https://help.aliyun.com/zh/dashscope/developer-reference/activate-dashscope-and-create-an-api-key)。接下来，请获取你的 [DASHSCOPE_API_KEY](https://dashscope.console.aliyun.com/apiKey)\n",
    "\n",
    "####  MacOS or Linux\n",
    "您可以使用以下命令行导入环境变量\n",
    "```bash\n",
    "export DASHSCOPE_API_KEY=\"sk-****\"\n",
    "```\n",
    "\n",
    "#### Windows\n",
    "可以在终端使用[`SET`](https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/set_1)命令设置环境变量\n",
    "```bat\n",
    "set DASHSCOPE_API_KEY=sk-****\n",
    "```\n",
    "或者在[`PowerShell`](https://learn.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.4)中使用以下命令行配置环境变量 \n",
    "```powershell\n",
    "$Env:DASHSCOPE_API_KEY = \"sk-****\"\n",
    "```\n",
    "\n",
    "#### Jupyter Notebook\n",
    "您可以使用[`os.environ`](https://docs.python.org/3/library/os.html)方法，在代码开头设置临时环境变量。\n",
    "\n",
    "### 1.3. docenv模式\n",
    "\n",
    "将环境变量写入文件\"~/.zshrc\"中：\n",
    "\n",
    "```bash\n",
    "export OPENAI_API_KEY=\"sk-...\"\n",
    "```\n",
    "就可以执行如下命令 ```source ~/.zshrc``` 将这个环境变量绑定到全局shell中。\n",
    "\n",
    "接下来我们将加载这个环境变量到notebook中。执行如下命令\n",
    "\n",
    "**使用Windows，已经通过Windows PowerShell来注册环境变量的同事可以考虑跳过这里**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Python-dotenv could not parse statement starting at line 7\n",
      "Python-dotenv could not parse statement starting at line 8\n",
      "Python-dotenv could not parse statement starting at line 10\n",
      "Python-dotenv could not parse statement starting at line 11\n",
      "Python-dotenv could not parse statement starting at line 16\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "from dotenv import load_dotenv\n",
    "## for MacOS users\n",
    "filePath = os.path.abspath(os.path.expanduser(os.path.expandvars(\"~/.zshrc\")))\n",
    "load_dotenv(filePath)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. MVP 开发\n",
    "在正式开发一个完整的程序之前，我们可以先开发一个最小可运行的程序，来验证一下效果。\n",
    "\n",
    "### 2.1 根据 URL 加载文档\n",
    "为了实现根据 URL 读取文档内容，你参考了 LangChain 的[这篇 WebBaseLoader 文档](https://python.langchain.com/docs/integrations/document_loaders/web_base)，并尝试在代码中使用 WebBaseLoader 来加载一篇文档："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "\n",
    "loader = WebBaseLoader(\"https://help.aliyun.com/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\")\n",
    "\n",
    "doc = loader.load()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 比较文档内容\n",
    "接下来你可以再加载一下国际站的文档，并将其交给通义千问帮你比对差别了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "以下是两篇文档的不同之处列表：\n",
      "\n",
      "1. 文档标题：\n",
      "   - 中国站文档：快速购买ECS实例_云服务器 ECS(ECS)-阿里云帮助中心\n",
      "   - 国际站文档：快速购买ECS实例 - 云服务器 ECS - 阿里云\n",
      "\n",
      "2. 文档内容差异：\n",
      "   - **前提条件**：\n",
      "     - 中国站文档提到“已注册账号并完成实名认证”，而国际站文档提到了“注册阿里云账号”。\n",
      "\n",
      "   - **默认配置**：\n",
      "     - 中国站文档列出的默认配置包括操作系统选项（Alibaba Cloud Linux、Windows Server、Ubuntu、CentOS），而国际站文档只提到CentOS。\n",
      "     - 可用的实例规格：\n",
      "       - 中国站文档提到了“实例规格族”和“ecs.s6-c1m1.small”，而国际站文档仅提到“突发性能实例t5”。\n",
      "       \n",
      "   - **网络类型**：\n",
      "     - 中国站文档提及“专有网络”，而国际站文档明确说明是“专有网络”。\n",
      "     - 公网IP的分配方式：\n",
      "       - 中国站文档提供两种选择（按固定带宽和按使用流量），国际站文档只有“按固定带宽”这一选项。\n",
      "\n",
      "   - **购买数量**：\n",
      "     - 中国站文档未提及购买数量的限制，而国际站文档明确提到可以购买的数量。\n",
      "\n",
      "   - **购买时长和自动续费**：\n",
      "     - 中国站文档让用户选择购买时长和是否自动续费，国际站文档让用户选择购买时长但未提及自动续费。\n",
      "\n",
      "   - **后续步骤**：\n",
      "     - 中国站文档提到了“反馈”链接，而国际站文档则提到“确认下单”过程，并且没有提及“反馈”。\n",
      "\n",
      "   - **文档末尾**：\n",
      "     - 中国站文档包含较多的联系方式、服务条款链接、政策声明等，国际站文档则相对简洁，只提到了服务条款链接。\n",
      "\n",
      "请注意，这些差异可能随时间和版本更新有所变化，具体信息请以最新文档为准。"
     ]
    }
   ],
   "source": [
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "from langchain_community.llms import Tongyi\n",
    "from utils.ds_util import sample_call_streaming\n",
    "\n",
    "llm = Tongyi()\n",
    "llm.model_name = 'qwen-max'\n",
    "\n",
    "\n",
    "def load_doc_content(url):\n",
    "    loader = WebBaseLoader(url)\n",
    "    doc = loader.load()\n",
    "    return doc[0].page_content\n",
    "\n",
    "\n",
    "doc_content_cn = load_doc_content(\"https://help.aliyun.com/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\")\n",
    "doc_content_intl = load_doc_content(\"https://www.alibabacloud.com/help/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\")\n",
    "\n",
    "prompt = f\"\"\"你现在的任务是找出下面两篇文档的不同之处，请不要放过任何细节。\n",
    "--------\n",
    "【中国站的文档】：\n",
    "{doc_content_cn}\n",
    "--------\n",
    "【国际站的文档】:\n",
    "{doc_content_intl}\n",
    "--------\n",
    "请给出结论，并用列表形式指出不同之处的具体位置：\n",
    "\"\"\"\n",
    "\n",
    "sample_call_streaming(prompt)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上就是一个 MVP 版本的程序了。它可以根据 URL 加载文档并进行比较，一定程度上，它已经能帮你减轻很多工作量了。\n",
    "\n",
    "## 3. 完善程序\n",
    "接下来我们可以完善一下程序，以达成我们最初设想的实现目标。\n",
    "\n",
    "### 3.1 遍历 Excel 中的文档 URL\n",
    "因为要比对的文档比较多，而且也经常会变，所以在代码中写死这些链接并不是一个好的做法。 我们可以在 excel 文件中管理 url，然后在程序中读取出来。\n",
    "\n",
    "参考代码如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "https://help.aliyun.com/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "https://www.alibabacloud.com/help/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "https://help.aliyun.com/zh/ecs/user-guide/overview-52\n",
      "https://www.alibabacloud.com/help/zh/ecs/user-guide/overview-52\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from openpyxl import load_workbook\n",
    "\n",
    "file_path = 'data/doc_urls.xlsx'\n",
    "\n",
    "wb = load_workbook(file_path)\n",
    "sheet = wb.active\n",
    "for row in sheet.iter_rows(values_only=True, min_row=2):\n",
    "    url_cn = row[0]\n",
    "    url_intl = row[0].replace('help.aliyun.com', 'www.alibabacloud.com/help')\n",
    "    print(url_cn)\n",
    "    print(url_intl)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你已经可以从 excel 中读取链接，并自动替换获取国际站的文档链接了。 我们可以对 MVP 程序进行一点封装，以便在遍历 excel 里的 url 时调用通义千问进行文档对比。 详细的代码可以查看：\n",
    "\n",
    "详细的代码可以查看：\n",
    "- [chapter2/load-from-excel/diff_docs.py](load-from-excel/diff_docs.py)\n",
    "- [chapter2/load-from-excel/main.py](load-from-excel/main.py)\n",
    "\n",
    "现在，你就可以运行程序，来实现批量自动文档对比了：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "------\n",
      "中国站文档：https://help.aliyun.com/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "国际站文档：https://www.alibabacloud.com/help/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "结论：两篇文档描述的是同一主题——快速购买阿里云ECS实例的流程，但存在一些细节差异，主要体现在产品细节、更新时间、操作步骤的细微差别、以及文档结构和布局的不同。\n",
      "\n",
      "不同之处具体位置列表：\n",
      "\n",
      "1. **文档标题和布局**\n",
      "   - 中国站文档标题较长，包含“快速购买ECS实例_云服务器 ECS(ECS)-阿里云帮助中心”，并且文档开头有较多的导航链接和广告内容。\n",
      "   - 国际站文档标题简洁为“快速购买ECS实例 - 云服务器 ECS - 阿里云”，文档开始直接进入主题，布局较为紧凑，导航元素相对较少。\n",
      "\n",
      "2. **更新时间**\n",
      "   - 中国站文档未明确给出更新时间。\n",
      "   - 国际站文档明确更新时间为“Nov 30, 2023”。\n",
      "\n",
      "3. **实例规格选项**\n",
      "   - 中国站文档在“产品规格”部分提及“ecs.s6-c1m1.small”作为示例规格，并指出规格选择与地域等因素有关，同时提到了四种操作系统支持（Alibaba Cloud Linux、Windows Server、Ubuntu、CentOS）。\n",
      "   - 国际站文档中一键购买仅支持“突发性能实例t5”，规格示例为“2 vCPU 4 GiB（突发性能实例t5）”，并提到三种操作系统支持（CentOS、Windows Server、Ubuntu），未提及Alibaba Cloud Linux。\n",
      "\n",
      "4. **网络带宽配置说明**\n",
      "   - 中国站文档在网络带宽配置时，直接列出“按固定带宽”和“按使用流量”的选择说明，而国际站文档在此处详细描述了选择公网带宽的两种模式后，直接给出默认选项“按固定带宽1 Mbps”。\n",
      "\n",
      "5. **服务条款和确认订单过程**\n",
      "   - 中国站文档在确认订单时，提及阅读《云服务器 ECS 服务条款》和《云服务器ECS退订说明》。\n",
      "   - 国际站文档则提及阅读《云服务器 ECS 服务条款》和《通用服务条款》。\n",
      "\n",
      "6. **文档结尾和反馈机制**\n",
      "   - 中国站文档末尾包含大量的公司信息、服务链接、法律声明等附加内容。\n",
      "   - 国际站文档以简短的“谢谢！我们已经收到了您的反馈。”结束，整体更加简洁。\n",
      "\n",
      "这些差异反映了针对不同地区用户习惯、产品策略和服务条款的调整。\n",
      "------\n",
      "中国站文档：https://help.aliyun.com/zh/ecs/user-guide/overview-52\n",
      "国际站文档：https://www.alibabacloud.com/help/zh/ecs/user-guide/overview-52\n",
      "经过仔细对比，两篇文档的内容实质上是相同的，都详细介绍了阿里云ECS实例的概述、基础配置、实例规格、镜像、存储、购买方式、使用指导及安全建议等内容。但是，在文档的布局、格式、以及一些辅助性信息上存在差异。以下是具体的不同之处列表：\n",
      "\n",
      "1. **文档标题和布局**：\n",
      "   - 中国站文档的标题结构为“实例概述_云服务器 ECS(ECS)-阿里云帮助中心”，并且文档开头有一系列导航链接，如“产品解决方案文档与社区权益中心定价云市场合作伙伴支持与服务了解阿里云备案控制台”等。\n",
      "   - 国际站文档的标题更为简洁，为“实例概述 - 云服务器 ECS - 阿里云”，文档开头则是一个搜索框和“全部产品”的链接。\n",
      "\n",
      "2. **更新时间格式**：\n",
      "   - 中国站文档的更新时间显示为“实例概述更新时间：一键部署产品详情相关技术圈我的收藏”，并未直接给出明确日期。\n",
      "   - 国际站文档直接给出了具体的更新日期：“更新时间：Jul 18, 2023”。\n",
      "\n",
      "3. **导航和搜索功能**：\n",
      "   - 中国站文档中有“文档产品文档输入文档关键字查找”以及一系列快速导航链接到不同产品和功能说明。\n",
      "   - 国际站文档则在顶部提供了一个统一的搜索框，用于搜索产品文档，并有“搜索本产品”和“全部产品”两个链接。\n",
      "\n",
      "4. **页面底部信息**：\n",
      "   - 中国站文档底部包含了丰富的公司信息、服务条款链接、备案信息、许可证明、联系方式等，并且展示了一系列阿里巴巴集团旗下的其他服务链接。\n",
      "   - 国际站文档底部信息相对精简，主要包含版权信息、更新日期和一个简单的反馈表单提示。\n",
      "\n",
      "综上所述，两份文档的主要区别在于界面布局、导航结构、更新时间的表达方式以及底部附加信息的详略程度，而关于ECS实例的核心内容描述基本一致。\n"
     ]
    }
   ],
   "source": [
    "! python load-from-excel/main.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 排除网页中的页头页尾等非关键内容\n",
    "细心的你可能会发现运行前面的代码时，通义千问给出的文档差异，可能还会包含一些页面尾部的差异，比如：\n",
    "```\n",
    "...\n",
    "2. 联系方式和链接格式：\n",
    "   - 中国站文档中提供了详细的联系方式，如“95187-1 在线服务”、“4008013260 在线服务”，并且附带了“我要建议”、“我要投诉”、“体验反馈”等功能入口，同时链接地址采用了中文描述且未直接展示超链接形式。\n",
    "   - 国际站文档没有在该处明确提供电话联系方式，而是集中在文档底部显示版权信息时提及，并且所有链接均采用英文描述及超链接形式展现。\n",
    "\n",
    "3. 页面底部信息：\n",
    "   - 中国站文档在底部包含了更多的附加信息，如“阿里云首页”、“相关技术圈”、“为什么选择阿里云”等，并列出了各类服务热线、法律声明、隐私权政策、廉正举报等链接。\n",
    "   - 国际站文档在底部只包含了版权信息、许可证编号以及指向其他阿里云产品的链接，联系方式较为简洁。\n",
    "...\n",
    "```\n",
    "\n",
    "出现这个情况的原因是，我们给到大模型的文档内容，其实是整个页面的内容。为了解决这一问题，我们需要想办法提取到文档正文本身，排除页头页尾等非文档正文内容。\n",
    "\n",
    "这里我们可以选择针对阿里云文档页面的结构，编写一个自定义的 loader："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "from bs4 import BeautifulSoup\n",
    "from langchain_community.document_loaders.base import BaseLoader\n",
    "from langchain_community.document_transformers import Html2TextTransformer\n",
    "from langchain_core.documents import Document\n",
    "import requests\n",
    "\n",
    "\n",
    "class AliyunDocLoader(BaseLoader):\n",
    "    \"\"\"\n",
    "    阿里云文档加载器\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, urls: List[str]):\n",
    "        self.urls = urls\n",
    "\n",
    "    def load(self) -> List[Document]:\n",
    "        results = []\n",
    "        for url in self.urls:\n",
    "            content, metadata = self._get_aliyun_help_doc(url)\n",
    "            doc = Document(page_content=content, metadata=metadata)\n",
    "            results.append(doc)\n",
    "        html2text = Html2TextTransformer()\n",
    "        return html2text.transform_documents(results)\n",
    "\n",
    "    @staticmethod\n",
    "    def _get_aliyun_help_doc(url: str):\n",
    "        \"\"\"\n",
    "        @param url: 文档 URL\n",
    "        @return: content, metadata. content 为 html，metadata 里包含 TDK\n",
    "        \"\"\"\n",
    "        html = requests.get(url).text\n",
    "        soup = BeautifulSoup(html, features='html.parser')\n",
    "        markdown_body = soup.find('article', class_='markdown-body') or soup.find('div', class_='markdown-body')\n",
    "        contents = markdown_body.contents\n",
    "        content_html = ''.join([str(item) for item in contents])\n",
    "        content_html = f'<h1>{soup.find(\"h1\").text}</h1>' + content_html\n",
    "\n",
    "        metadata = {\n",
    "            \"title\": soup.find('title').text,\n",
    "            \"keywords\": soup.find('meta', attrs={'name': 'keywords'}).attrs['content'],\n",
    "            \"description\": soup.find('meta', attrs={'name': 'description'}).attrs['content']\n",
    "        }\n",
    "        return content_html, metadata"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "完整的代码在 [`chapter2/custom-loader/`](custom-loader/) 中。\n",
    "现在你可以尝试运行一下使用自定义阿里云文档 loader 的对比程序了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "------\n",
      "中国站文档：https://help.aliyun.com/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "国际站文档：https://www.alibabacloud.com/help/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "根据对比分析，以下是两篇文档中的不同之处：\n",
      "\n",
      "1. 文档标题：\n",
      "   - 中国站文档：快速购买包年包月实例\n",
      "   - 国际站文档：云服务器 ECS：一键购买包年包月实例\n",
      "\n",
      "2. 操作步骤中关于产品规格的说明和示例：\n",
      "   - 中国站文档：\n",
      "     > 产品规格\n",
      "     > 可选的实例规格和地域等因素有关，您可以在 **使用说明** 列，查看该实例规格适用的场景。如需更多实例规格，请前往自定义购买。\n",
      "     > 示例：ecs.s6-c1m1.small\n",
      "   - 国际站文档：\n",
      "     > 实例规格\n",
      "     > 一键购买仅支持突发性能实例t5，如需更多实例规格，请前往自定义购买。\n",
      "     > 示例：2 vCPU 4 GiB（突发性能实例t5）\n",
      "\n",
      "3. 操作系统和预装应用的选择：\n",
      "   - 中国站文档提供了操作系统和预装应用两种选择，并指出二者只能选其一。\n",
      "   - 国际站文档未提及预装应用选项，只提供镜像选择，且列举了三种操作系统：CentOS、Windows Server、Ubuntu。\n",
      "\n",
      "4. 公网带宽计费模式及配置的展示方式：\n",
      "   - 中国站文档将公网IP和带宽计费模式分开描述，并列举了两种带宽计费模式的具体内容。\n",
      "   - 国际站文档在“公网带宽”部分同时包含了公网IP分配和带宽计费模式的选择，且整合在一个列表项中展示。\n",
      "\n",
      "5. 购买时长和自动续费的展示方式：\n",
      "   - 中国站文档将购买时长和自动续费作为两个独立参数分别列出。\n",
      "   - 国际站文档将购买时长和是否启用自动续费合并在一起作为一个整体进行展示。\n",
      "\n",
      "6. 确认订单环节中的服务条款：\n",
      "   - 中国站文档提到的服务条款为《云服务器 ECS 服务条款》和《云服务器ECS退订说明》。\n",
      "   - 国际站文档提到的服务条款为《云服务器 ECS 服务条款》和《通用服务条款》。\n",
      "\n",
      "7. 确认订单按钮的文字表述：\n",
      "   - 中国站文档使用的是“确认订单”按钮。\n",
      "   - 国际站文档使用的是“确认下单”按钮。\n",
      "------\n",
      "中国站文档：https://help.aliyun.com/zh/ecs/user-guide/overview-52\n",
      "国际站文档：https://www.alibabacloud.com/help/zh/ecs/user-guide/overview-52\n",
      "经过比对，两篇文档内容完全一致，没有任何不同之处。\n"
     ]
    }
   ],
   "source": [
    "! python custom-loader/main.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3 应对超大文档\n",
    "以上的程序似乎已经比较完善了。但很快你就会发现，有一些文档非常大，超出通义千问 API 的最大 token 数限制了。\n",
    "\n",
    "### 3.3.1 更换模型\n",
    "经过查阅阿里云的 [DashScope - 通义千问模型](https://help.aliyun.com/zh/dashscope/developer-reference/api-details)说明文档，你会发现正在使用的`qwen-max`最大 token 数是 6k。\n",
    "另外，你还发现`qwen-plus`和`qwen-max-longcontext`支持的 token 数分别是 30k 和 28k。\n",
    "所以最简单的办法就是修改现有的代码，使用`qwen-plus`和`qwen-max-longcontext`即可。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "llm = Tongyi()\n",
    "llm.model_name = 'qwen-max-longcontext'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "尽管上面的代码改动已经解决了你遇到的问题，但严谨的你一定会想到，如果遇到了更大的文档，还是超过了 token 数量限制该怎么办。\n",
    "\n",
    "### 3.3.2 分而治之\n",
    "我们可以借鉴 MapReduce 的思想，将文档分成多个小文档分片，然后对每个分片做对比，最后汇总结果。\n",
    "\n",
    "具体实现思路是：\n",
    "1. 我们可以先找出两篇文档中相同的二级标题；\n",
    "2. 然后以这些相同的二级标题对文档进行切片；\n",
    "3. 对每个切片进行对比，并将结果汇总，得到最终结果。\n",
    "\n",
    "我们在[`chapter2/long-docs/doc_spliter.py`](long-docs/doc_spliter.py)中提供了两个方法，用于找到相同的二级标题，和对文档进行切片。\n",
    "另外在[`chapter2/long-docs/diff_docs.py`](long-docs/diff_docs.py)中调整了对比程序的实现，程序会先对第一个文档切片进行对比，然后带着对比结论，对剩余文档切片进行对比，最终遍历完所有的切片时，就得到了最终答案。\n",
    "\n",
    "这是切片提问模式下的两段提示词：\n",
    "```python\n",
    "def get_question_prompt(doc_content_cn, doc_content_intl):\n",
    "    return f\"\"\"你现在的任务是找出下面两段文档的不同之处，请不要放过任何细节。\n",
    "--------\n",
    "【中国站的文档】：\n",
    "{doc_content_cn}\n",
    "--------\n",
    "【国际站的文档】:\n",
    "{doc_content_intl}\n",
    "--------\n",
    "请给出结论，并用列表形式指出不同之处的具体位置：\n",
    "\"\"\"\n",
    "\n",
    "\n",
    "def get_refine_prompt(doc_content_cn, doc_content_intl, existing_answer):\n",
    "    return f\"\"\"这是你在前面的文档片段中找出的不同：\n",
    "{existing_answer}\n",
    "--------\n",
    "请找出下面两个文档片段不同之处的具体位置，并补充到之前的结果中：\n",
    "--------\n",
    "【中国站的文档】：\n",
    "{doc_content_cn}\n",
    "--------\n",
    "【国际站的文档】:\n",
    "{doc_content_intl}\n",
    "\"\"\"\n",
    "```\n",
    "\n",
    "你可以运行一下试试看："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "------\n",
      "中国站文档：https://help.aliyun.com/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "国际站文档：https://www.alibabacloud.com/help/zh/ecs/user-guide/create-a-subscription-instance-on-the-quick-launch-tab\n",
      "正在对比第1个片段...\n",
      "正在对比第2个片段...\n",
      "正在对比第3个片段...\n",
      "正在对比第4个片段...\n",
      "经过比对，两个文档片段在内容上存在以下不同之处：\n",
      "\n",
      "1. 创建ECS实例时选择产品规格的描述：\n",
      "   - 中国站文档中：可选产品规格ecs.s6-c1m1.small，并提供了查看使用说明的提示。\n",
      "   - 国际站文档中：一键购买仅支持突发性能实例t5。\n",
      "\n",
      "2. 操作系统和预装应用部分：\n",
      "   - 中国站文档中：详细列出了操作系统（包括Alibaba Cloud Linux、Windows Server、Ubuntu、CentOS）和预装应用，并特别说明二者只能选择其中一个。\n",
      "   - 国际站文档中：只提及一键购买支持的操作系统镜像类型（CentOS、Windows Server、Ubuntu），未提及预装应用的选择。\n",
      "\n",
      "3. 带宽计费模式与带宽值的呈现方式：\n",
      "   - 中国站文档中：将带宽计费模式（按固定带宽或按使用流量）和带宽值分开表述。\n",
      "   - 国际站文档中：公网带宽部分整合了公网IP分配、带宽计费模式选择（按固定带宽）以及具体的带宽值（1 Mbps）。\n",
      "\n",
      "4. 购买时长和自动续费选项的顺序和表述：\n",
      "   - 中国站文档中：先列出购买时长，再列出自动续费选项。\n",
      "   - 国际站文档中：购买数量后面直接是购买时长及其与自动续费的一体化选择。\n",
      "\n",
      "5. 确认订单步骤中的页面名称和协议内容：\n",
      "   - 中国站文档中：在确认订单面板阅读《云服务器 ECS 服务条款》|《云服务器ECS退订说明》，然后确认订单。\n",
      "   - 国际站文档中：在 **确认订单** 页面阅读《云服务器 ECS 服务条款》|《通用服务条款》，然后确认下单。\n",
      "------\n",
      "中国站文档：https://help.aliyun.com/zh/ecs/user-guide/overview-52\n",
      "国际站文档：https://www.alibabacloud.com/help/zh/ecs/user-guide/overview-52\n",
      "正在对比第1个片段...\n",
      "正在对比第2个片段...\n",
      "正在对比第3个片段...\n",
      "正在对比第4个片段...\n",
      "正在对比第5个片段...\n",
      "正在对比第6个片段...\n",
      "正在对比第7个片段...\n",
      "经过仔细对比，发现两个文档片段内容完全一致，无任何不同之处。\n"
     ]
    }
   ],
   "source": [
    "! python long-docs/main.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 总结\n",
    "通过本章的学习，你已经能借助通义千问的 API 来帮助自己完成一些原来要人工完成的工作了，也学会了通过分而治之的思路来解决提示词过长的问题。分而治之的思想在很多场景下都有应用，除了用于做两篇长文档的对比，也可以用于超长文档的总结。\n",
    "\n",
    "需要注意的时，这里出于学习的目的，文档的切片方法并不是最优的：\n",
    "- 提示词长度没有超限时，是可以不用切片的；\n",
    "- 一些特殊的文档也可能存在一个二级标题下内容也超过 token 限制，实际用于生产时，需要结合具体业务来定制切片方法。\n",
    "\n",
    "另外，在实际使用中，你也可以使用 LangChain 提供的 refine 类型的 load_summarize_chain 来完成这个切片后调用大模型的任务。详情可以参考：[LangChain Use cases：Summarization - Refine](https://python.langchain.com/docs/use_cases/summarization#option-3.-refine)。\n",
    "\n",
    "\n",
    "## 5. 参考资料\n",
    "- [DashScope](https://dashscope.aliyun.com/)\n",
    "- [通义千问 - 模型概览](https://help.aliyun.com/zh/dashscope/developer-reference/api-details)\n",
    "- [LangChain](https://python.langchain.com/docs)\n",
    "- [LangChain Use cases：Summarization - Refine](https://python.langchain.com/docs/use_cases/summarization#option-3.-refine)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "chatllm",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
